/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <net/ethernet.h>

#include <glib.h>

#include <connman/blob.h>

#include "connman.h"

#define	_DBG_WIFI(fmt, arg...)	DBG(DBG_WIFI, fmt, ## arg)

static inline gboolean ispsk(const char *security)
{
	return (g_strcmp0(security, "wpa") == 0 ||
		g_strcmp0(security, "rsn") == 0 ||
		g_strcmp0(security, "psk") == 0);
}

/*
 * Construct a ``group name'' when creating a wifi service (see
 * __connman_get_wifi_service_int).  This string incorporates the
 * SSID, security, and operating mode of all devices that should
 * be grouped together.  Note that we promote all PSK security
 * settings to "psk" so services created before the device shows
 * up in a scan result can be located when their associated
 * network is created.
 */
char *connman_wifi_build_group_name(const unsigned char *ssid,
						unsigned int ssid_len,
							const char *mode,
							const char *security)
{
	GString *str;
	unsigned int i;

	str = g_string_sized_new((ssid_len * 2) + 24);
	if (str == NULL)
		return NULL;

	if (ssid_len > 0 && ssid[0] != '\0') {
		for (i = 0; i < ssid_len; i++)
			g_string_append_printf(str, "%02x", ssid[i]);
	}

	/* NB: use "psk" for all wpa/rsn/psk services */
	g_string_append_printf(str, "_%s_%s", mode,
	    ispsk(security) ? "psk" : security);

	return g_string_free(str, FALSE);
}

/*
 * Table of well-known SSID's that we want to disambiguate when
 * constructing service identifiers.  Normally all devices with
 * the same SSID, security setup, and operating mode will be
 * merged to the same service.  A service advertising one of
 * these SSID's will have a unique group name constructed that
 * ensures like-devices will not be grouped together.
 * 
 * TODO(sleffler) externalize this table and make it's use optional
 */
static struct {
	char *name;
	char *value;
} special_ssid[] = {
	{ "<hidden>", "hidden"  },
	{ "default",  "linksys" },
	{ "wireless"  },
	{ "linksys"   },
	{ "netgear"   },
	{ "dlink"     },
	{ "2wire"     },
	{ "compaq"    },
	{ "tsunami"   },
	{ "comcomcom", "3com"     },
	{ "3Com",      "3com"     },
	{ "Symbol",    "symbol"   },
	{ "Motorola",  "motorola" },
	{ "Wireless" , "wireless" },
	{ "WLAN",      "wlan"     },
	{ }
};

/*
 * Like connman_wifi_build_group_name but used in the wifi plugins
 * when creating network objects from scan results.  The group name
 * must match the strings constructed by connman_wifi_build_group_name
 * so the associated services are matched up.  This routine differs
 * in that it handles hidden SSID's and does uniquification of
 * devices that beacon well-known SSID's that we don't want to merge
 * into a group.
 */
char *connman_wifi_build_group(const char *addr, const char *name,
			const unsigned char *ssid, unsigned int ssid_len,
					const char *mode, const char *security)
{
	GString *str;
	unsigned int i;

	if (addr == NULL)
		return NULL;

	str = g_string_sized_new((ssid_len * 2) + 24);
	if (str == NULL)
		return NULL;

	if (ssid == NULL) {
		g_string_append_printf(str, "hidden_%s", addr);
		goto done;
	}

	for (i = 0; special_ssid[i].name; i++) {
		if (g_strcmp0(special_ssid[i].name, name) == 0) {
			if (special_ssid[i].value == NULL)
				g_string_append_printf(str, "%s_%.*s",
				    name, 2*ETH_ALEN, addr);
			else
				g_string_append_printf(str, "%s_%.*s",
				    special_ssid[i].value, 2*ETH_ALEN, addr);
			goto done;
		}
	}

	if (ssid_len > 0 && ssid[0] != '\0') {
		for (i = 0; i < ssid_len; i++)
			g_string_append_printf(str, "%02x", ssid[i]);
	} else
		g_string_append_printf(str, "hidden_%.*s", 2*ETH_ALEN, addr);
done:
	/* NB: use "psk" for all wpa/rsn/psk services */
	g_string_append_printf(str, "_%s_%s", mode,
	    ispsk(security) ? "psk" : security);

	return g_string_free(str, FALSE);
}

/*
 * NB: this knows a bit too much about how services are written to the profile.
 */
static void append_hidden_ssids(GKeyFile *keyfile, GSList **hidden_ssids)
{
	gchar **keys;

	keys = g_key_file_get_groups(keyfile, NULL);
	if (keys == NULL) {
		connman_warn("%s: unable to retrieve groups", __func__);
		return;
	}

	for (; *keys != NULL; keys++) {
		gchar *key = *keys;
		gchar *hex_ssid;
		struct blob *ssid;
		int ret;

		if (g_ascii_strncasecmp(key, "wifi_", 5) != 0)
			continue;

		if (!g_key_file_get_boolean(keyfile, key, "WiFi.HiddenSSID",
				NULL))
			continue;

		hex_ssid = g_key_file_get_string(keyfile, key, "SSID", NULL);
		if (hex_ssid == NULL)
			continue;

		_DBG_WIFI("service %s", key);

		ret = blob_new_from_hex(&ssid, hex_ssid);
		g_free(hex_ssid);
		if (ret < 0) {
			connman_warn("%s: skip hex ssid %s for key %s",
			    __func__, hex_ssid, key);
			continue;
		}

		_DBG_WIFI("ssid %.*s", (int) ssid->len, ssid->data);
		*hidden_ssids = g_slist_prepend(*hidden_ssids, ssid);
	}
}

/**
 * connman_wifi_append_hidden_ssids:
 * @ssids: pointer to a list of hidden SSIDs
 *
 * Append the list of SSIDs whose services whose WiFi.HiddenSSID property is
 * TRUE.  These SSIDs should be directly probed whenever performing a WiFi
 * network scan.
 */
void connman_wifi_append_hidden_ssids(GSList **ssids)
{
	__connman_profile_append_hidden_ssids(ssids, append_hidden_ssids);
}
